package com.evan.idempotent.distributedlock;

import com.evan.idempotent.exception.IdempotentException;
import com.evan.idempotent.warpper.IdempotentRunnable;
import com.evan.idempotent.warpper.IdempotentSupplier;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.client.WriteRedisConnectionException;
import org.springframework.beans.factory.annotation.Autowired;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * @Description: redis分布式锁实现
 * @Author liudeguang
 */
@Slf4j
@AllArgsConstructor
@NoArgsConstructor
public class DistributedLockRedis implements DistributedLock {


    private RedissonClient redissonClient;

    @Override
    public <T> T lock(String key, long waitTime, long leaseTime, IdempotentSupplier<T> success, IdempotentSupplier<T> fail) {
        return doLock(key, waitTime, leaseTime, TimeUnit.MILLISECONDS, success, fail);
    }

    @Override
    public <T> T lock(String key, long leaseTime, IdempotentSupplier<T> success, IdempotentSupplier<T> fail) {
        return doLock(key, 0, leaseTime, TimeUnit.MILLISECONDS, success, fail);
    }

    @Override
    public <T> T lock(String key, long leaseTime, TimeUnit timeUnit, IdempotentSupplier<T> success, IdempotentSupplier<T> fail) {
        return doLock(key, 0, leaseTime, timeUnit, success, fail);
    }

    @Override
    public void lock(String key, long waitTime, long leaseTime, IdempotentRunnable success, IdempotentRunnable fail) {
        doLock(key, waitTime, leaseTime, TimeUnit.MILLISECONDS, success, fail);
    }

    @Override
    public void lock(String key, long leaseTime, IdempotentRunnable success, IdempotentRunnable fail) {
        doLock(key, 0, leaseTime, TimeUnit.MILLISECONDS, success, fail);
    }

    @Override
    public void lock(String key, long leaseTime, TimeUnit timeUnit, IdempotentRunnable success, IdempotentRunnable fail) {
        doLock(key, 0, leaseTime, timeUnit, success, fail);
    }


    private <T> T doLock(String key, long waitTime, long leaseTime, TimeUnit timeUnit, IdempotentSupplier<T> success, IdempotentSupplier<T> fail) {
        RLock rLock = null;
        try {
            try {
                rLock = redissonClient.getLock(key);
            } catch (Exception e) {
                log.error("DistributedLockRedis-doLock-supplier get Redis Lock Error ", e);
                return fail.get(key, new IdempotentException(e));
            }
            if (rLock == null) {
                log.error("DistributedLockRedis-doLock-supplier Redis Lock is null ");
                return fail.get(key, new IdempotentException("Redis Lock is null"));
            }
            boolean tryLock = false;
            try {
                tryLock = rLock.tryLock(waitTime, leaseTime, timeUnit);
            } catch (WriteRedisConnectionException e) {
                log.error("DistributedLockRedis-doLock-supplier lock writeRedisConnectionException error ", e);
                throw new IdempotentException(e);
            } catch (InterruptedException e) {
                log.error("DistributedLockRedis-doLock-supplier lock interrupted error ", e);
                throw new IdempotentException(e);
            }
            if (!tryLock) {
                return fail.get(key, new IdempotentException("DistributedLockRedis-doLock-supplier tryLock is fail "));
            }
            try {
                // 这个执行出错，直接抛异常了
                return success.get(key, null);
            } catch (Exception e) {
                throw new IdempotentException(e);
            }
        } catch (Exception e) {
            log.error("DistributedLockRedis-doLock-supplier lock error ", e);
            throw new IdempotentException(e);
        } finally {
            if (rLock != null && rLock.isLocked() && rLock.isHeldByCurrentThread()) {
                rLock.unlock();
            }
        }
    }

    private void doLock(String key, long waitTime, long leaseTime, TimeUnit timeUnit, IdempotentRunnable success, IdempotentRunnable fail) {
        RLock rLock = null;
        try {
            try {
                rLock = redissonClient.getLock(key);
            } catch (Exception e) {
                log.error("DistributedLockRedis-doLock-runnable get Redis Lock Error ", e);
                fail.run(key, new IdempotentException(e));
            }
            if (rLock == null) {
                log.debug("DistributedLockRedis-doLock-runnable Redis Lock is null ");
                fail.run(key, new IdempotentException("DistributedLockRedis-doLock-runnable Redis Lock is null"));
                return;
            }
            boolean tryLock = false;
            try {
                tryLock = rLock.tryLock(waitTime, leaseTime, timeUnit);
            } catch (WriteRedisConnectionException e) {
                log.error("DistributedLockRedis-doLock-runnable lock writeRedisConnectionException error ", e);
                throw new IdempotentException(e);
            } catch (InterruptedException e) {
                log.error("DistributedLockRedis-doLock-runnable lock interrupted error ", e);
                throw new IdempotentException(e);
            } catch (NullPointerException e) {
                log.error("DistributedLockRedis-doLock-runnable get tryLock nullPointerException error ", e);
                throw new IdempotentException(e);
            }
            if (!tryLock) {
                fail.run(key, null);
            }
            try {
                // 这个执行出错，直接抛异常了
                success.run(key, null);
            } catch (Exception e) {
                throw new IdempotentException(e);
            }
        } catch (Exception e) {
            log.error("DistributedLockRedis-doLock-runnable lock error ", e);
            throw new IdempotentException(e);
        } finally {
            if (rLock != null && rLock.isLocked() && rLock.isHeldByCurrentThread()) {
                rLock.unlock();
            }
        }
    }

}
